// Andrew Hickey & Steven Steffen
// CS412 Rahimi
// Summer 2010
// Project Server program.
// This file handles both the main server and all four client-handler threads as part of the player class.
// A DM player will also have an array of five monsters that will act together on the DM's turn
import java.awt.*;
import java.awt.event.*;
import java.net.*;
import java.io.*;
import javax.swing.*;
import java.util.*;

public class Server{
   private Player players[];
   private ServerSocket server;
   private int currentPlayer;
   private Monster monsters[];

   public Server()
   {
      players = new Player[ 4 ];
	  monsters = new Monster[5];
      currentPlayer = 0;
 
      // set up ServerSocket
      try {
         server = new ServerSocket( 5000, 2 );
      }
      catch( IOException e ) {
         e.printStackTrace();
         System.exit( 1 );
      }
   }

   // wait for two connections so game can be played
   public void execute()
   {
      for ( int i = 0; i < players.length; i++ ) {
         try {
            players[ i ] =
               new Player( server.accept(), this, i );
            players[ i ].start();
         }
         catch( IOException e ) {
            e.printStackTrace();
            System.exit( 1 );
         }
      }
	//Shuffle player orders based on init for random turn order.
	System.out.println("Before Sort:");
	printPlayerOrder(players);
	Arrays.sort(players);
	for(int i = 0; i < 4; i++)
	{
		players[i].setOrder(i);
	}
	System.out.println("After Sort:");
	printPlayerOrder(players);
	

      // Players are suspended until Wizard connects.
      // Resume players now.          
	for(int i = 0; i < 4; i++)
	{
		synchronized (players[i])
		{
       		players[ i ].threadSuspended = false;
	 		players[i].notify();
		}
	}
	  
   }
 
   // Determine if a move is valid.
   // This method is synchronized because only one move can be
   // made at a time.
	// Player Move Controller
   public synchronized boolean playerValidMove( String command,
                                          int player )
   {
      boolean moveDone = false;
	  int source;
	  int[] targets = new int[5];
	  char action;
	  String[] actionParts = command.split(":");
	  String[] targetString;
	
	  //Parse read strings in format (Source [player#]):(Action [a=attack]):(Targets [monster#-monster#...])
	  source = Integer.parseInt(actionParts[0]);
	  action = actionParts[1].charAt(0);
	  targetString = actionParts[2].split("-");
	  for(int i = 0; i < targetString.length; i++)
	  {
		targets[i] = Integer.parseInt(targetString[i]);
	  }
	  System.out.println("Player " + source + " using action " + action + " against monster: " + targets[0]);

      while ( player != currentPlayer ) {
         try {
            wait();
         }
         catch( InterruptedException e ) {
            e.printStackTrace();
         }
      }

      if ( true ) {
         currentPlayer = ( currentPlayer + 1 ) % 4;
         players[ currentPlayer ].otherPlayerMoved( command );
         notifyAll();    // tell waiting players to continue
         return true;
      }
      else 
         return false;
   }
	
	// DM Move Controller
	public synchronized boolean dmValidMove( String command,
	                                          int player )
	   {
	      boolean moveDone = false;
		  int source, target;
		  char action;
		  String[] monsterActions = command.split(":");
		  String[] actionParts;
	      while ( player != currentPlayer ) {
	         try {
	            wait();
	         }
	         catch( InterruptedException e ) {
	            e.printStackTrace();
	         }
	      }
		//DM Action String: 0-a-1:1-a-1:...etc (Source [monster#])-(Action[a = attack])-(Target [player#])
		//No error checking because the interface will not allow for error cases to even take place
		for(int i = 0; i < monsterActions.length; i++)
		{
			actionParts = monsterActions[i].split("-");
			source = Integer.parseInt(actionParts[0]);
			target = Integer.parseInt(actionParts[2]);
			action = actionParts[1].charAt(0);
			System.out.println("Taking Action: Monster" + source + " using " + action + " on player " + target);
		}
	      if ( true ) {
	         currentPlayer = ( currentPlayer + 1 ) % 4;
	         players[ currentPlayer ].otherPlayerMoved( command );
	         notifyAll();    // tell waiting players to continue
	         return true;
	      }
	      else 
	         return false;
	   }

   /*public boolean isOccupied( int loc )
   {
      if ( board[ loc ] == 'X' || board [ loc ] == 'O' )
          return true;
      else
          return false;
   }*/
   
   public String playerAttack(Player player, Monster monster)
   {
		Random r = new Random((new Date()).getTime());
		int d20 = r.nextInt(20) + 1;
		int att = d20 + player.getAttack();
		if(att >= monster.getDefense())
		{
			int dam = r.nextInt(8) + 1;
			monster.takeDamage(dam);
			return "Hit:" + att + ":" + dam;
		}
		else {return "Miss:" + att;}
	}
	
	// Monster Attack
	// Essentially the same, but differences are crutial to balance
	public String monsterAttack(Monster monster, Player player)
   {
		Random r = new Random((new Date()).getTime());
		int d20 = r.nextInt(20) + 1;
		int att = d20 + monster.getAttack();
		if(att >= player.getDefense())
		{
			int dam = r.nextInt(4) + 1;
			player.takeDamage(dam);
			return "Hit:" + att + ":" + dam;
		}
		else {return "Miss:" + att;}
	}
	
   public boolean gameOver()
   {
      // Place code here to test for a winner of the game
      return false;
   }

   public static void main( String args[] )
   {
      Server game = new Server();
      game.execute();
   }

	// Debugging Methods
	public void printPlayerOrder(Player[] players)
	{
		for(int i = 0; i < players.length; i++)
		{
			System.out.println("Player " + players[i].getNumber() + " with initiative " + players[i].getInit() + " and order place " + players[i].getOrder());
		}
	}
}

// Player class to manage each Player as a thread
class Player extends Thread implements Comparable {
   private Socket connection;
   private DataInputStream input;
   private DataOutputStream output;
   private Server control;
   private String charClass, name; 
   private String[] classOrder = {"Dungeon Master", "Warrior", "Healer", "Wizard"};
   private int number, order, hp, attack, defense, initiative;
   private Random generate;
   protected boolean threadSuspended = true, alive;

   public Player( Socket s, Server t, int num )
   {
      charClass = classOrder[num];

      connection = s;
      
      try {
         input = new DataInputStream(
                    connection.getInputStream() );
         output = new DataOutputStream(
                    connection.getOutputStream() );
      }
      catch( IOException e ) {
         e.printStackTrace();
         System.exit( 1 );
      }
	  // Initialize stats. If time permits we will add some more features involving stats.
	  generate = new Random(new Date().getTime());
	  initiative = generate.nextInt(20) + 1;
      control = t;
      number = num;
	  hp = 50;
	  defense = 17;
	  attack = 5;
	  alive = true;
   }

	// Required Overload of compareTo method
	public int compareTo(Object obj)
	{
		Player tmp = (Player)obj;
		return tmp.initiative - this.initiative;
	}

   public void otherPlayerMoved( String command )
   {
      try {
         output.writeUTF( "Opponents moved" );
         output.writeUTF( command );
      }
      catch ( IOException e ) { e.printStackTrace(); }
   }

   // Player attribute modifiers
   public void takeDamage(int damage)
   {
		hp -= damage;
		if (hp < 1)
			alive = false;
   }
   
   public void heal(int heal)
   {
		hp += heal;
		if (hp > 50)
			hp = 50;
   }
   
   public boolean isLive(){return alive;}
   public int getAttack(){return attack;}
   public int getDefense(){return defense;}
   public int getNumber(){return number;}
   public int getInit(){return initiative;}
   public int getOrder(){return order;}

   public void setOrder(int ord){this.order = ord;}
   
	// Main Communications with Client
   public void run()
   {
      boolean done = false;

      try {
         output.writeUTF( charClass );
         /*output.writeUTF( "Player " +
            ( number == 0 ? "X connected\n" :
                            "O connected, please wait\n" ) );*/

         // wait for another player to arrive
         if ( charClass == "Dungeon Master" || charClass == "Warrior" || charClass == "Healer" ) {
            try {
               synchronized( this ) {   
                  while ( threadSuspended ){
					 System.out.println(number + " Waiting.");
                     wait();
				  }
               }
            } 
            catch ( InterruptedException e ) {
               e.printStackTrace();
            }
			//System.out.println("Out of Try block");
			if(this.order == 0)
			{
				System.out.println("Writing to DM to start turn");
				output.writeUTF("Other players connected. Your turn." );
				System.out.println("Wrote to DM to start turn");
			}
         }
		 if (charClass != "Dungeon Master")
		 {
			 // Play Heroes' game
			 while ( !done ) {
				String command = input.readUTF();

				if ( control.playerValidMove( command, order ) ) {
				   output.writeUTF( "Valid move. ");
				}
				else 
				   output.writeUTF( "Invalid move, try again" );

				if ( control.gameOver() )
				   done = true;
			 }         
		 }
		 else
		 {
			// Play DM's game
			 while ( !done ) {
				String command = input.readUTF();

				if ( control.dmValidMove( command, number ) ) {
				   output.writeUTF( "Valid move. ");
				}
				else 
				   output.writeUTF( "Invalid move, try again" );

				if ( control.gameOver() )
				   done = true;
			 }
		}
         connection.close();
		 
      }
      catch( IOException e ) {
         e.printStackTrace();
         System.exit( 1 );
      }
   }
}



